#!/usr/bin/env python
# -*- coding: utf-8 -*-

import random
from typing import Dict, List, Tuple, Any, Optional

class Scheduler:
    """Handles the automatic scheduling of physical education classes."""
    
    def __init__(self, max_students_per_day: int = 8000, 
                 time_slots: Optional[List[str]] = None,
                 days: Optional[List[str]] = None):
        """
        Initialize the scheduler.
        
        Args:
            max_students_per_day: Maximum number of students allowed per day
            time_slots: List of time slots (e.g., ['第1-2节', '第3-4节', '第5-6节'])
            days: List of days (e.g., ['星期一', '星期二', ..., '星期五'])
        """
        self.max_students_per_day = max_students_per_day
        self.time_slots = time_slots or ['第1-2节', '第3-4节', '第5-6节']
        self.days = days or ['星期一', '星期二', '星期三', '星期四', '星期五']
        self.scheduled_classes = []  # List of (class_info, row, col) tuples
        self.daily_students = {day: 0 for day in self.days}  # Track students per day
        
    def auto_schedule(self, unscheduled_classes: List[Dict[str, Any]], 
                      schedule_table: Any, parent: Any) -> Tuple[bool, str]:
        """
        Automatically schedule the unscheduled classes.
        
        Args:
            unscheduled_classes: List of unscheduled class dictionaries
            schedule_table: Reference to the schedule table widget
            parent: Parent widget for showing progress
            
        Returns:
            Tuple of (success: bool, message: str)
        """
        if not unscheduled_classes:
            return True, "没有需要安排的课程"
            
        try:
            # Make a copy to avoid modifying the original list
            remaining_classes = unscheduled_classes.copy()
            scheduled_count = 0
            iteration = 0
            max_iterations = 2  # Maximum number of scheduling attempts
            
            while remaining_classes and iteration < max_iterations:
                iteration += 1
                # Group classes by college, major and grade
                grouped_classes = self._group_classes_by_college_major_grade(remaining_classes)
                
                # Sort groups by total students (descending)
                sorted_groups = sorted(
                    grouped_classes.items(),
                    key=lambda x: sum(cls['total'] for cls in x[1]),
                    reverse=True
                )
                
                # Try to schedule each group
                for (college, major, grade), classes in sorted_groups:
                    # First try to schedule the group together
                    success = self._schedule_group(classes, schedule_table, parent)
                    if success:
                        scheduled_count += len(classes)
                        remaining_classes = [c for c in remaining_classes 
                                           if c not in classes]
                    else:
                        # If group scheduling fails, try scheduling individually
                        for cls in classes:
                            if self._schedule_group([cls], schedule_table, parent):
                                scheduled_count += 1
                                if cls in remaining_classes:
                                    remaining_classes.remove(cls)
                
                # If we've scheduled everything, we're done
                if not remaining_classes:
                    break
                    
                # If we still have classes left, try with different grouping
                if iteration == 1:
                    # Try grouping just by college and grade
                    grouped_classes = self._group_classes_by_college_grade(remaining_classes)
                    sorted_groups = sorted(
                        grouped_classes.items(),
                        key=lambda x: sum(cls['total'] for cls in x[1]),
                        reverse=True
                    )
                    
                    for (college, grade), classes in sorted_groups:
                        if self._schedule_group(classes, schedule_table, parent):
                            scheduled_count += len(classes)
                            remaining_classes = [c for c in remaining_classes 
                                               if c not in classes]
            
            if remaining_classes:
                return False, f"成功安排 {scheduled_count} 个班级，{len(remaining_classes)} 个班级未能安排"
                
            return True, f"成功安排 {scheduled_count} 个班级"
            
        except Exception as e:
            import traceback
            traceback.print_exc()
            return False, f"排课过程中出错: {str(e)}"
    
    def _group_classes_by_college_grade(self, classes: List[Dict[str, Any]]) -> Dict[Tuple[str, str], List[Dict[str, Any]]]:
        """Group classes by college and grade."""
        groups = {}
        for cls in classes:
            key = (cls['college'], str(cls['grade']))
            if key not in groups:
                groups[key] = []
            groups[key].append(cls)
        return groups
        
    def _group_classes_by_college_major_grade(self, classes: List[Dict[str, Any]]) -> Dict[Tuple[str, str, str], List[Dict[str, Any]]]:
        """Group classes by college, major and grade."""
        groups = {}
        for cls in classes:
            key = (cls['college'], cls['major'], str(cls['grade']))
            if key not in groups:
                groups[key] = []
            groups[key].append(cls)
        return groups
    
    def _schedule_group(self, classes: List[Dict[str, Any]], 
                        schedule_table: Any, parent: Any) -> bool:
        """
        Schedule a group of classes from the same college and grade.
        
        Args:
            classes: List of class dictionaries to schedule
            schedule_table: Reference to the schedule table widget
            parent: Parent widget for showing progress
            
        Returns:
            bool: True if all classes were scheduled successfully
        """
        # Try to find the best time slot for this group
        best_slot = self._find_best_time_slot(classes)
        
        if best_slot is None:
            return False
            
        row, col = best_slot
        
        # Schedule all classes in this group at the same time
        for cls in classes:
            if not schedule_table.add_class_to_cell(row, col, cls):
                return False
            self.scheduled_classes.append((cls, row, col))
            self.daily_students[self.days[col]] += cls.get('total', 0)
            
            # Update the parent's unscheduled classes list
            if hasattr(parent, 'unscheduled_classes') and cls in parent.unscheduled_classes:
                parent.unscheduled_classes.remove(cls)
        
        return True
    
    def _find_best_time_slot(self, classes: List[Dict[str, Any]]) -> Optional[Tuple[int, int]]:
        """
        Find the best time slot for a group of classes.
        
        Args:
            classes: List of class dictionaries
            
        Returns:
            Tuple of (row, col) for the best time slot, or None if no slot is available
        """
        total_students = sum(cls.get('total', 0) for cls in classes)
        
        # First, try to find a slot where the group can be scheduled together
        best_slot = self._find_available_slot(classes, total_students)
        if best_slot is not None:
            return best_slot
            
        # If no single slot is available, try to split the group if possible
        if len(classes) > 1:
            # Try to find a slot for each class individually
            for cls in classes:
                slot = self._find_available_slot([cls], cls.get('total', 0))
                if slot is not None:
                    return slot
        
        return None
        
    def _find_available_slot(self, classes: List[Dict[str, Any]], total_students: int) -> Optional[Tuple[int, int]]:
        """Find an available time slot for the given classes."""
        # Try each day
        for col, day in enumerate(self.days):
            # Check daily student limit
            if self.daily_students[day] + total_students > self.max_students_per_day:
                continue
                
            # Try each time slot
            for row in range(len(self.time_slots)):
                if self._is_time_slot_available(row, col, classes):
                    return (row, col)
        return None
    
    def _is_time_slot_available(self, row: int, col: int, 
                              classes: List[Dict[str, Any]]) -> bool:
        """
        Check if a time slot is available for the given classes.
        
        Args:
            row: Time slot row index
            col: Day column index
            classes: List of class dictionaries
            
        Returns:
            bool: True if the time slot is available
        """
        # Check if any class is already scheduled at this time
        for scheduled_cls, r, c in self.scheduled_classes:
            if r == row and c == col:
                # Check if any of the new classes conflict with the scheduled one
                for new_cls in classes:
                    # Same college and grade can't be scheduled at the same time
                    if (new_cls['college'] == scheduled_cls['college'] and 
                        new_cls['grade'] == scheduled_cls['grade']):
                        return False
                        
                    # Same class can't be scheduled twice
                    if (new_cls['college'] == scheduled_cls['college'] and 
                        new_cls['major'] == scheduled_cls['major'] and 
                        new_cls['grade'] == scheduled_cls['grade']):
                        return False
                        
        return True
    
    def get_scheduled_classes(self) -> List[Dict[str, Any]]:
        """Get a list of all scheduled classes."""
        return [cls for cls, _, _ in self.scheduled_classes]
    
    def get_daily_students(self) -> Dict[str, int]:
        """Get the number of students scheduled per day."""
        return self.daily_students.copy()
